﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace AOC19NET.Controls {
	/// <summary>
	/// Interaction logic for Histogram.xaml
	/// </summary>
	public partial class Histogram : UserControl {
		/*-------------------------------------------------------------------------*/
		/* DEFINITIONS															   */
		/*-------------------------------------------------------------------------*/
		#region DEFINITIONS
			/// <summary>
			/// We'll keep a fixed amount of values buffered.
			/// </summary>
			private const int NUM_VALUES = 180;	// 3 minutes of data if updated once per second
		#endregion
	
		/*-------------------------------------------------------------------------*/
		/* MEMBERS																   */
		/*-------------------------------------------------------------------------*/
		#region MEMBERS
		/// <summary>
		/// Automatically regulate max height.
		/// </summary>
		private float m_fMaxValue = 0.0f;

		/// <summary>
		/// Array of histogram values.
		/// </summary>
		private List<float> m_vValues = new List<float>();
		#endregion

		/*-------------------------------------------------------------------------*/
		/* METHODS																   */
		/*-------------------------------------------------------------------------*/
		#region METHODS
		/// <summary>
		/// Default constructor.
		/// </summary>
		public Histogram() {
			InitializeComponent();

			m_fMaxValue = 100;

			// Initialize Values list with 0s
			for(int i = 0; i < NUM_VALUES; i++) {
				m_vValues.Add(0.0f);
			}
		}

		/// <summary>
		/// Add a new value to the end of the values list. Will remove the first one.
		/// This function also updates the histogram, so it should be called periodically.
		/// </summary>
		/// <param name="_fNewValue">New value.</param>
		public void NewValue(float _fNewValue) {
			// 1. Update Max - no longer, we don't want auto-adjustnt anymore.
			/*
			if(_fNewValue > m_fMaxValue) {
				// 1.1. The new value is the new maximum: just update it.
				m_fMaxValue = _fNewValue;
			} else if(m_vValues[0] == m_fMaxValue) {
				// 1.2. The value to be removed was the maximum: find a new one
				m_fMaxValue = m_vValues.Max();
			}
			*/

			// 2. Remove first value
			m_vValues.RemoveAt(0);

			// 3. Add new value at the end of the list
			m_vValues.Add(_fNewValue);

			// 4. Update histogram
			UpdateHistogram();
		}

		/// <summary>
		/// Sets a new maximum value for the histogram. The graph will be reescaled according
		/// to this value.
		/// </summary>
		/// <param name="_fNewMax">The new maximum.</param>
		public void SetMaxValue(float _fNewMax) {
			if(m_fMaxValue > 0) {
				m_fMaxValue = _fNewMax;
			}
		}

		/// <summary>
		/// Updates the polyline with new data.
		/// </summary>
		private void UpdateHistogram() {
			// Clear the graph and add the new list of points
			// The graph will be auto-scaled thanks to the Stretch="Fill" property
			m_GraphLine.Points.Clear();
			for(int i = 0; i < NUM_VALUES; i++) {
				// Clamp the value between 0 and m_fMaxValue
				float fVal = m_vValues[i];
				if(fVal > m_fMaxValue) {
					fVal = m_fMaxValue;
				} else if(fVal < 0) {
					fVal = 0;
				}

				// The Y coordinated must be inverted to see the graph straight
				m_GraphLine.Points.Add(new Point(i, m_fMaxValue - fVal));
			}

			// Add a new point with m_fMaxValue value at the end to correctly scale the graph
			m_GraphLine.Points.Add(new Point(NUM_VALUES - 1, (m_fMaxValue - m_fMaxValue)));

			// Add a new point with 0 value at the beginning and at the end to correctly fill the graph
			m_GraphLine.Points.Insert(0, new Point(0, (m_fMaxValue - 0.0f)));
			m_GraphLine.Points.Add(new Point(NUM_VALUES - 1, (m_fMaxValue - 0.0f)));
		}

		/// <summary>
		/// Set the fill color.
		/// </summary>
		/// <param name="_NewColor">New fill color.</param>
		public void SetFillColor(Color _NewColor) {
			m_GraphLine.Fill = new SolidColorBrush(_NewColor);
		}

		/// <summary>
		/// Set the stroke color and thickness.
		/// </summary>
		/// <param name="_NewColor">New stroke color.</param>
		/// <param name="_dNewThickness">New stroke thickness.</param>
		public void SetStroke(Color _NewColor, double _dNewThickness) {
			m_GraphLine.Stroke = new SolidColorBrush(_NewColor);
			if(_dNewThickness > 0) {
				m_GraphLine.StrokeThickness = _dNewThickness;
			}
		}

		/// <summary>
		/// Sets the color and thickness of the horizontal axis.
		/// </summary>
		/// <param name="_NewColor">New color for the X axis.</param>
		/// <param name="_dNewThickness">New thickness for the X axis.</param>
		public void SetAxisX(Color _NewColor, double _dNewThickness) {
			m_AxisX.Fill = new SolidColorBrush(_NewColor);
			if(_dNewThickness > 0) {
				m_AxisXContainer.Height = new GridLength(_dNewThickness);
			}
		}

		/// <summary>
		/// Sets the color and thickness of the vertical axis.
		/// </summary>
		/// <param name="_NewColor">New color for the Y axis.</param>
		/// <param name="_dNewThickness">New thickness for the Y axis.</param>
		public void SetAxisY(Color _NewColor, double _dNewThickness) {
			m_AxisY.Fill = new SolidColorBrush(_NewColor);
			if(_dNewThickness > 0) {
				m_AxisYContainer.Width = new GridLength(_dNewThickness);
			}
		}
		#endregion

		/*-------------------------------------------------------------------------*/
		/* PROPERTIES															   */
		/*-------------------------------------------------------------------------*/
		#region LINE COLOR PROPERTY
		/// <summary>
		/// "LineColor" property accessor.
		/// </summary>
		public Brush LineColor {
			get {
				return (Brush)GetValue(LineColorProperty);
			}
			set {
				SetValue(LineColorProperty, (Brush)value);
			}
		}

		/// <summary>
		/// "LineColor" property definition and registration.
		/// </summary>
		public static DependencyProperty LineColorProperty =
				DependencyProperty.Register(
				"LineColorProperty", typeof(Brush), typeof(Histogram),
				new PropertyMetadata(Brushes.Black,
					new PropertyChangedCallback(LineColorPropertyChangedCallback)
				)
			);

		/// <summary>
		/// "LineColor" property change callback.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_Arguments">Event arguments.</param>
		public static void LineColorPropertyChangedCallback(DependencyObject _Sender, DependencyPropertyChangedEventArgs _Arguments) {
			Histogram Sender = (Histogram)_Sender;
			Sender.m_GraphLine.Stroke = (Brush)_Arguments.NewValue;
		}
		#endregion

		#region LINE THICKNESS PROPERTY
		/// <summary>
		/// "LineThickness" property accessor.
		/// </summary>
		public double LineThickness {
			get {
				return (double)GetValue(LineThicknessProperty);
			}
			set {
				SetValue(LineThicknessProperty, (double)value);
			}
		}

		/// <summary>
		/// "LineThickness" property definition and registration.
		/// </summary>
		public static DependencyProperty LineThicknessProperty =
				DependencyProperty.Register(
				"LineThicknessProperty", typeof(double), typeof(Histogram),
				new PropertyMetadata(1.0,
					new PropertyChangedCallback(LineThicknessPropertyChangedCallback)
				)
			);

		/// <summary>
		/// "LineThickness" property change callback.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_Arguments">Event arguments.</param>
		public static void LineThicknessPropertyChangedCallback(DependencyObject _Sender, DependencyPropertyChangedEventArgs _Arguments) {
			Histogram Sender = (Histogram)_Sender;
			Sender.m_GraphLine.StrokeThickness = (double)_Arguments.NewValue;
		}
		#endregion

		#region FILL COLOR PROPERTY
		/// <summary>
		/// "FillColor" property accessor.
		/// </summary>
		public Brush FillColor {
			get {
				return (Brush)GetValue(FillColorProperty);
			}
			set {
				SetValue(FillColorProperty, (Brush)value);
			}
		}

		/// <summary>
		/// "FillColor" property definition and registration.
		/// </summary>
		public static DependencyProperty FillColorProperty =
				DependencyProperty.Register(
				"FillColorProperty", typeof(Brush), typeof(Histogram),
				new PropertyMetadata(Brushes.Transparent,
					new PropertyChangedCallback(FillColorPropertyChangedCallback)
				)
			);

		/// <summary>
		/// "FillColor" property change callback.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_Arguments">Event arguments.</param>
		public static void FillColorPropertyChangedCallback(DependencyObject _Sender, DependencyPropertyChangedEventArgs _Arguments) {
			Histogram Sender = (Histogram)_Sender;
			Sender.m_GraphLine.Fill = (Brush)_Arguments.NewValue;
		}
		#endregion
	}
}
